"""
C++ parser for code analysis.

Provides comprehensive C++ parsing using libclang for accurate AST analysis
and extraction of code elements including functions, classes, templates, and dependencies.
"""

from typing import Dict, List, Any, Optional, Set, Union
from pathlib import Path
import logging

from ...core.error_handler import AnalysisError, log_info, log_debug
from ...parsers.cpp.clang_wrapper import ClangWrapper, CompilationConfig
from ...parsers.cpp.ast_visitor import CppASTVisitor
from ...parsers.cpp.symbol_table import SymbolTable, Symbol, SymbolKind


class CppParser:
    """C++ parser for code analysis using libclang."""
    
    def __init__(self, config: CompilationConfig = None):
        """Initialize the C++ parser."""
        self.config = config or CompilationConfig()
        self.clang_wrapper = ClangWrapper(self.config)
        self.symbol_table = SymbolTable()
        self.current_file = None
        
        # Parse statistics
        self.stats = {
            'files_parsed': 0,
            'functions_found': 0,
            'classes_found': 0,
            'templates_found': 0,
            'parse_errors': 0
        }
    
    def parse_file(self, file_path: str, unsaved_files: Dict[str, str] = None) -> Dict[str, Any]:
        """
        Parse a C++ file and extract all relevant information.
        
        Args:
            file_path: Path to the C++ file to parse
            unsaved_files: Dictionary of unsaved file contents
            
        Returns:
            Dictionary containing extracted code elements
        """
        self.current_file = file_path
        file_path = str(Path(file_path).resolve())
        
        log_info(f"Parsing C++ file: {file_path}")
        
        try:
            # Parse with libclang
            parse_result = self.clang_wrapper.parse_file(file_path, unsaved_files)
            
            if not parse_result.success:
                self.stats['parse_errors'] += 1
                if parse_result.error_message:
                    log_debug(f"Parse errors in {file_path}: {parse_result.error_message}")
                
                # Return partial results even with errors
                return {
                    'file_path': file_path,
                    'parse_successful': False,
                    'error_message': parse_result.error_message,
                    'diagnostics': parse_result.diagnostics,
                    'functions': [],
                    'classes': [],
                    'templates': [],
                    'namespaces': [],
                    'includes': [],
                    'variables': [],
                    'typedefs': [],
                    'enums': [],
                    'function_calls': [],
                    'method_calls': [],
                    'constructor_calls': [],
                    'template_instantiations': [],
                    'inheritance_relations': [],
                    'using_declarations': []
                }
            
            # Reset symbol table for this file
            self.symbol_table.reset()
            
            # Visit AST with visitor
            visitor = CppASTVisitor()
            ast_data = visitor.visit_translation_unit(parse_result.translation_unit, self.clang_wrapper)
            
            # Build symbol table
            self._build_symbol_table(ast_data)
            
            # Post-process results
            processed_result = self._post_process_results(ast_data, file_path)
            
            # Update statistics
            self.stats['files_parsed'] += 1
            self.stats['functions_found'] += len(processed_result['functions'])
            self.stats['classes_found'] += len(processed_result['classes'])
            self.stats['templates_found'] += len(processed_result['templates'])
            
            return processed_result
            
        except Exception as e:
            self.stats['parse_errors'] += 1
            log_debug(f"Error parsing C++ file {file_path}: {e}")
            raise AnalysisError(f"Failed to parse C++ file {file_path}: {e}")
    
    def _build_symbol_table(self, ast_data: Dict[str, Any]):
        """Build symbol table from AST data."""
        try:
            # Add namespaces first
            for namespace in ast_data['namespaces']:
                self._add_namespace_to_symbols(namespace)
            
            # Add classes and their members
            for class_info in ast_data['classes']:
                self._add_class_to_symbols(class_info)
            
            # Add functions
            for function in ast_data['functions']:
                self._add_function_to_symbols(function)
            
            # Add variables
            for variable in ast_data['variables']:
                self._add_variable_to_symbols(variable)
            
            # Add typedefs and type aliases
            for typedef in ast_data['typedefs']:
                self._add_typedef_to_symbols(typedef)
            
            # Add enums
            for enum in ast_data['enums']:
                self._add_enum_to_symbols(enum)
            
            # Add using declarations
            for using_decl in ast_data['using_declarations']:
                self._add_using_to_symbols(using_decl)
                
        except Exception as e:
            log_debug(f"Error building symbol table: {e}")
    
    def _add_namespace_to_symbols(self, namespace_info: Dict[str, Any]):
        """Add namespace to symbol table."""
        symbol = Symbol(
            name=namespace_info['name'],
            kind=SymbolKind.NAMESPACE,
            qualified_name=namespace_info['qualified_name'],
            location=namespace_info['location'],
            namespace=namespace_info.get('parent_namespace', '').split('::') if namespace_info.get('parent_namespace') else []
        )
        
        # Enter namespace scope
        if namespace_info['name'] != '<anonymous>':
            self.symbol_table.enter_scope(namespace_info['name'], "namespace", namespace_info['location'])
            self.symbol_table.add_symbol(symbol)
    
    def _add_class_to_symbols(self, class_info: Dict[str, Any]):
        """Add class to symbol table."""
        symbol = Symbol(
            name=class_info['name'],
            kind=SymbolKind.CLASS if class_info['kind'] == 'class' else 
                  SymbolKind.STRUCT if class_info['kind'] == 'struct' else SymbolKind.UNION,
            qualified_name=class_info['qualified_name'],
            location=class_info['location'],
            namespace=class_info['namespace'],
            is_template=class_info['is_template'],
            template_info=class_info['template_info'],
            access_level=class_info['access_level'],
            attributes=class_info['attributes'],
            usr=class_info['usr'],
            metadata={
                'base_classes': class_info['base_classes'],
                'is_abstract': class_info['is_abstract'],
                'is_final': class_info['is_final']
            }
        )
        
        self.symbol_table.add_symbol(symbol)
    
    def _add_function_to_symbols(self, function_info: Dict[str, Any]):
        """Add function to symbol table."""
        # Determine function kind
        if 'class_name' in function_info and function_info['class_name']:
            if function_info['name'] == function_info['class_name']:
                kind = SymbolKind.CONSTRUCTOR
            elif function_info['name'].startswith('~'):
                kind = SymbolKind.DESTRUCTOR
            else:
                kind = SymbolKind.METHOD
        else:
            kind = SymbolKind.FUNCTION
        
        symbol = Symbol(
            name=function_info['name'],
            kind=kind,
            qualified_name=function_info['qualified_name'],
            location=function_info['location'],
            type_info={
                'return_type': function_info.get('return_type', {}),
                'parameters': function_info.get('parameters', [])
            },
            namespace=function_info['namespace'],
            class_name=function_info.get('class_name'),
            access_level=function_info['access_level'],
            is_template=function_info['is_template'],
            template_info=function_info['template_info'],
            attributes=function_info['attributes'],
            usr=function_info['usr'],
            metadata={
                'is_virtual': function_info.get('is_virtual', False),
                'is_pure_virtual': function_info.get('is_pure_virtual', False),
                'is_static': function_info.get('is_static', False),
                'is_const': function_info.get('is_const', False),
                'is_override': function_info.get('is_override', False),
                'is_final': function_info.get('is_final', False),
                'is_inline': function_info.get('is_inline', False),
                'is_extern': function_info.get('is_extern', False),
                'is_definition': function_info.get('is_definition', False)
            }
        )
        
        self.symbol_table.add_symbol(symbol)
    
    def _add_variable_to_symbols(self, variable_info: Dict[str, Any]):
        """Add variable to symbol table."""
        kind = SymbolKind.FIELD if variable_info.get('class_name') else SymbolKind.VARIABLE
        
        symbol = Symbol(
            name=variable_info['name'],
            kind=kind,
            qualified_name=variable_info['qualified_name'],
            location=variable_info['location'],
            type_info=variable_info['type'],
            namespace=variable_info['namespace'],
            class_name=variable_info.get('class_name'),
            usr=variable_info['usr'],
            metadata={
                'is_global': variable_info.get('is_global', False),
                'is_static': variable_info.get('is_static', False),
                'is_extern': variable_info.get('is_extern', False),
                'is_const': variable_info.get('is_const', False)
            }
        )
        
        self.symbol_table.add_symbol(symbol)
    
    def _add_typedef_to_symbols(self, typedef_info: Dict[str, Any]):
        """Add typedef to symbol table."""
        symbol = Symbol(
            name=typedef_info['name'],
            kind=SymbolKind.TYPEDEF,
            qualified_name=typedef_info['qualified_name'],
            location=typedef_info['location'],
            type_info=typedef_info['underlying_type'],
            namespace=typedef_info['namespace'],
            usr=typedef_info['usr']
        )
        
        self.symbol_table.add_symbol(symbol)
    
    def _add_enum_to_symbols(self, enum_info: Dict[str, Any]):
        """Add enum to symbol table."""
        symbol = Symbol(
            name=enum_info['name'],
            kind=SymbolKind.ENUM,
            qualified_name=enum_info['qualified_name'],
            location=enum_info['location'],
            namespace=enum_info['namespace'],
            class_name=enum_info.get('class_name'),
            usr=enum_info['usr'],
            metadata={
                'is_scoped': enum_info.get('is_scoped', False),
                'underlying_type': enum_info.get('underlying_type', {}),
                'values': enum_info.get('values', [])
            }
        )
        
        self.symbol_table.add_symbol(symbol)
        
        # Add enum constants
        for value in enum_info.get('values', []):
            const_symbol = Symbol(
                name=value['name'],
                kind=SymbolKind.ENUM_CONSTANT,
                qualified_name=f"{enum_info['qualified_name']}::{value['name']}",
                location=value['location'],
                namespace=enum_info['namespace'],
                class_name=enum_info.get('class_name'),
                metadata={'enum_value': value.get('value'), 'parent_enum': enum_info['qualified_name']}
            )
            self.symbol_table.add_symbol(const_symbol)
    
    def _add_using_to_symbols(self, using_info: Dict[str, Any]):
        """Add using declaration/directive to symbol table."""
        if using_info['type'] == 'declaration':
            self.symbol_table.add_using_declaration(using_info['name'])
        elif using_info['type'] == 'directive':
            self.symbol_table.add_using_directive(using_info['namespace'])
    
    def _post_process_results(self, ast_data: Dict[str, Any], file_path: str) -> Dict[str, Any]:
        """Post-process AST results with additional analysis."""
        result = ast_data.copy()
        result['file_path'] = file_path
        result['parse_successful'] = True
        result['symbol_table_stats'] = self.symbol_table.get_statistics()
        
        # Enhance function calls with resolved information
        enhanced_calls = []
        for call in ast_data['function_calls']:
            enhanced_call = call.copy()
            
            # Try to resolve the called function
            resolved_symbol = self.symbol_table.resolve_function_call(
                call['function_name'],
                call.get('arguments', [])
            )
            
            if resolved_symbol:
                enhanced_call['resolved_function'] = {
                    'qualified_name': resolved_symbol.qualified_name,
                    'location': resolved_symbol.location,
                    'is_template': resolved_symbol.is_template,
                    'usr': resolved_symbol.usr
                }
            
            enhanced_calls.append(enhanced_call)
        
        result['function_calls'] = enhanced_calls
        
        # Enhance method calls
        enhanced_methods = []
        for call in ast_data['method_calls']:
            enhanced_call = call.copy()
            
            # Try to resolve the method through type analysis
            object_type = call.get('object_type', {})
            if object_type.get('spelling'):
                class_name = object_type['spelling']
                class_members = self.symbol_table.get_class_members(class_name)
                
                for member in class_members:
                    if (member.name == call['method_name'] and 
                        member.kind == SymbolKind.METHOD):
                        enhanced_call['resolved_method'] = {
                            'qualified_name': member.qualified_name,
                            'location': member.location,
                            'is_virtual': member.metadata.get('is_virtual', False),
                            'usr': member.usr
                        }
                        break
            
            enhanced_methods.append(enhanced_call)
        
        result['method_calls'] = enhanced_methods
        
        # Add scope information
        result['scope_tree'] = self._build_scope_tree()
        
        # Add template instantiation analysis
        result['template_analysis'] = self._analyze_templates(ast_data)
        
        # Add include dependency graph
        result['include_graph'] = self._build_include_graph(ast_data['includes'])
        
        return result
    
    def _build_scope_tree(self) -> Dict[str, Any]:
        """Build a hierarchical scope tree."""
        def scope_to_dict(scope):
            return {
                'name': scope.name,
                'kind': scope.kind,
                'location': scope.location,
                'symbol_count': len(scope.symbols),
                'using_declarations': scope.using_declarations,
                'using_directives': scope.using_directives,
                'template_parameters': scope.template_parameters,
                'children': [scope_to_dict(child) for child in scope.children]
            }
        
        return scope_to_dict(self.symbol_table.global_scope)
    
    def _analyze_templates(self, ast_data: Dict[str, Any]) -> Dict[str, Any]:
        """Analyze template usage and instantiations."""
        template_analysis = {
            'declarations': [],
            'instantiations': [],
            'specializations': [],
            'metaprogramming_patterns': []
        }
        
        # Collect template declarations
        for func in ast_data['functions']:
            if func['is_template']:
                template_analysis['declarations'].append({
                    'name': func['name'],
                    'kind': 'function_template',
                    'parameters': func['template_info'].get('parameters', []),
                    'location': func['location']
                })
        
        for cls in ast_data['classes']:
            if cls['is_template']:
                template_analysis['declarations'].append({
                    'name': cls['name'],
                    'kind': 'class_template',
                    'parameters': cls['template_info'].get('parameters', []),
                    'location': cls['location']
                })
        
        # Analyze template instantiations
        for instantiation in ast_data['template_instantiations']:
            template_analysis['instantiations'].append(instantiation)
        
        # Detect metaprogramming patterns
        template_analysis['metaprogramming_patterns'] = self._detect_metaprogramming_patterns(ast_data)
        
        return template_analysis
    
    def _detect_metaprogramming_patterns(self, ast_data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Detect common C++ metaprogramming patterns."""
        patterns = []
        
        # Look for SFINAE patterns
        for func in ast_data['functions']:
            if func['is_template'] and func['template_info']:
                params = func['template_info'].get('parameters', [])
                for param in params:
                    if 'enable_if' in param.get('name', '').lower():
                        patterns.append({
                            'pattern': 'SFINAE_enable_if',
                            'location': func['location'],
                            'function': func['name']
                        })
        
        # Look for tag dispatch patterns
        for cls in ast_data['classes']:
            if (cls['name'].endswith('_tag') or 
                cls['name'].endswith('_type') or
                cls['name'].startswith('type_')):
                patterns.append({
                    'pattern': 'tag_dispatch',
                    'location': cls['location'],
                    'class': cls['name']
                })
        
        return patterns
    
    def _build_include_graph(self, includes: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Build include dependency graph."""
        graph = {
            'nodes': [],
            'edges': [],
            'system_includes': [],
            'local_includes': []
        }
        
        for include in includes:
            node = {
                'file': include['file'],
                'location': include['location'],
                'is_system': include.get('is_system_include', False)
            }
            graph['nodes'].append(node)
            
            if include.get('is_system_include', False):
                graph['system_includes'].append(include['file'])
            else:
                graph['local_includes'].append(include['file'])
            
            # Add edge from current file to included file
            graph['edges'].append({
                'from': self.current_file,
                'to': include['file'],
                'location': include['location']
            })
        
        return graph
    
    def get_symbol_by_name(self, name: str) -> Optional[Symbol]:
        """Get a symbol by name."""
        return self.symbol_table.find_symbol(name)
    
    def get_symbol_by_qualified_name(self, qualified_name: str) -> Optional[Symbol]:
        """Get a symbol by qualified name."""
        return self.symbol_table.find_qualified_symbol(qualified_name)
    
    def get_class_hierarchy(self, class_name: str) -> List[str]:
        """Get inheritance hierarchy for a class."""
        return self.symbol_table.get_inheritance_hierarchy(class_name)
    
    def find_function_overloads(self, function_name: str) -> List[Symbol]:
        """Find all overloads of a function."""
        return self.symbol_table.get_function_overloads(function_name)
    
    def resolve_function_call(self, function_name: str, 
                             arguments: List[Dict[str, Any]] = None) -> Optional[Symbol]:
        """Resolve a function call to the best matching overload."""
        return self.symbol_table.resolve_function_call(function_name, arguments)
    
    def get_namespace_members(self, namespace_name: str) -> List[Symbol]:
        """Get all members of a namespace."""
        return self.symbol_table.get_namespace_symbols(namespace_name)
    
    def get_class_members(self, class_name: str) -> List[Symbol]:
        """Get all members of a class."""
        return self.symbol_table.get_class_members(class_name)
    
    def is_supported_file(self, file_path: str) -> bool:
        """Check if file is supported by this parser."""
        path = Path(file_path)
        supported_extensions = {
            '.cpp', '.cxx', '.cc', '.c++', '.C',  # C++ source files (may include HIP with hipcc)
            '.hpp', '.hxx', '.hh', '.h++', '.H',  # C++ header files
            '.h',                                 # C/C++ header files
            '.ipp', '.inl', '.inc',              # Inline/include files
            '.cu', '.cuh'                        # CUDA source and header files
        }
        return path.suffix.lower() in supported_extensions
    
    def get_supported_extensions(self) -> List[str]:
        """Get list of supported file extensions."""
        return [
            '.cpp', '.cxx', '.cc', '.c++', '.C',
            '.hpp', '.hxx', '.hh', '.h++', '.H',
            '.h', '.ipp', '.inl', '.inc',
            '.cu', '.cuh'  # CUDA extensions
        ]
    
    def get_parser_info(self) -> Dict[str, Any]:
        """Get information about this parser."""
        return {
            'language': 'cpp',
            'parser_type': 'libclang',
            'cpp_standard': self.config.standard,
            'supported_extensions': self.get_supported_extensions(),
            'features': [
                'Template analysis',
                'Inheritance hierarchy',
                'Virtual function resolution',
                'Namespace resolution',
                'Include dependency tracking',
                'Modern C++17/20 syntax',
                'SFINAE pattern detection',
                'Metaprogramming analysis'
            ],
            'statistics': self.stats,
            'symbol_table_stats': self.symbol_table.get_statistics()
        }
    
    def cleanup(self):
        """Clean up parser resources."""
        if self.clang_wrapper:
            self.clang_wrapper.cleanup()